﻿using System;
using System.Collections.Generic;
using System.Linq;
using CI.Interfaces;
using System.Runtime.Serialization;
using CI.Organism;

namespace CI.Mesopotamia
{
    /// <summary>
    /// Simple simulation restrictions
    /// </summary>
    public class SimpleSimulationRestrictions : ISimulationRestrictions
    {
        protected readonly List<IOrganism> DeadList = new List<IOrganism>();
        protected readonly List<IOrganism> AliveList = new List<IOrganism>();
        protected readonly List<IOrganism> SeedsAliveList = new List<IOrganism>();
        protected readonly List<IOrganism> NeuralAliveList = new List<IOrganism>();
        /// <summary>
        /// Sim environment instance
        /// </summary>
        protected readonly SimEnv SimEnv = SimEnv.Instance;

        /// <summary>delete every nth element</summary>
        [DataMember]
        private int _deleteNthElement = 5;
        private int _deleteCounter;

        /// <summary>Inactive list values</summary>
        [DataMember]
        private int _inactiveKeep = 10;
        [DataMember]
        private int _inactiveKeepCullThreshold = 20;

        // how many system heartbeats we wait before checking
        [DataMember]
        protected UInt32 _checkForSeedsMinSystemHB = 10;

        /// <summary>
        /// Check and apply any restrictions to the simulation that should apply, eg within max entitys, 
        /// not just seeds, neural or non neural existing
        /// </summary>
        public virtual void RestrictionChecking(IHostingEnvironment hostingEnvironment)
        {
            #region Argument Validators
            if (hostingEnvironment == null)
                throw new ArgumentException("hostingEnvironment");
            #endregion
            // we only want one thread checking the restrictions and trimming at once else we could end up in trouble
            lock (this)
            {
                CheckJustNotSeeds(hostingEnvironment);

                // check the inactive count, if to many dump them
                CheckInactiveCount(hostingEnvironment);

                //Cull if the entity consumed is over the max entitys supported in the environment
                CullOrganismsIfOverMaxEntitysSupported(hostingEnvironment);
            }
        }

        /// <summary>
        /// Check and make sure their are not just seeds left in a hosting environment
        /// </summary>
        private void CheckJustNotSeeds(IHostingEnvironment hostingEnvironment)
        {
            lock (this)
            {
                // this is to check that that we dont have a simulation full of seeds only
                if (hostingEnvironment.SystemHeartBeat > _checkForSeedsMinSystemHB)
                {
                    // reset lists
                    SeedsAliveList.Clear();
                    SeedsAliveList.AddRange(hostingEnvironment.Organisms.Alive);

                    bool activeOrg = SeedsAliveList.Any(organism => hostingEnvironment.HostingEnvironmentPhysical.Registered(organism));

                    // check each organism

                    // if their are no active organism then break
                    if (activeOrg == false)
                    {
                        const string restartMsg = "Only seeds left in simulation, breaking";
                        SimEnv.TraceLogger.LogDebug(restartMsg);
                        SimEnv.TriggerScenarioRestart(restartMsg);
                    }
                }
            }
        }


        /// <summary>
        /// Check and make sure we dont have to many inactive organisms, this is to help with memory consumption reduction
        /// </summary>
        public void CheckInactiveCount(IHostingEnvironment hostingEnvironment)
        {
            #region Argument Validators
            if (hostingEnvironment == null)
                throw new ArgumentException("hostingEnvironment");
            #endregion

            lock (this)
            {
                // trim inactive just to cut down on the amount in the environment
                if (hostingEnvironment.Organisms.Inactive.Count > _inactiveKeepCullThreshold)
                {
                    // save the inactive
                    IList<IOrganism> inactive = hostingEnvironment.Organisms.Inactive;
                    // save the results of inactive trimmed
                    IList<IOrganism> trimmed = TrimInactive(hostingEnvironment.Organisms.Inactive, _inactiveKeep, hostingEnvironment.FitnessFunction);

                    // get some memmory back
                    foreach (IOrganism organism in inactive)
                    {
                        // if we kept it
                        if (trimmed.Contains(organism))
                            continue;

                        // else remove associations
                        organism.ParentOrganisms.Clear();
                    }

                    // todo : refactor, must be better way to do this
                    hostingEnvironment.Organisms.Inactive.Clear();
                    foreach (IOrganism organism in trimmed)
                    {
                        hostingEnvironment.Organisms.InactiveAdd(organism);
                    }
                }
            }
        }

        /// <summary>
        /// Cull if the entity consumed is over the max entitys supported in the environment
        /// </summary>
        public void CullOrganismsIfOverMaxEntitysSupported(IHostingEnvironment hostingEnvironment)
        {
            // now trim from the dead list the ratio
            SimEnv.TraceLogger.LogDebug(string.Format("Restriction Checking Environment entitys consumed : {0}", hostingEnvironment.HostingEnvironmentPhysical.EntitysConsumed));
            SimEnv.TraceLogger.LogDebug(string.Format("Restriction Checking Environment max entitys supported : {0}", hostingEnvironment.HostingEnvironmentPhysical.MaxEntitysSupported));

            if (hostingEnvironment.HostingEnvironmentPhysical.EntitysConsumed > hostingEnvironment.HostingEnvironmentPhysical.MaxEntitysSupported)
            {
                // work out how many to cut from the alive collection
                PurgeOrganismCollection(hostingEnvironment, hostingEnvironment.Organisms.Dead, DeadList, "Dead");
                
                PurgeOrganismCollection(hostingEnvironment, hostingEnvironment.Organisms.Alive, AliveList, "Alive");
            }

            // check and make sure we are still under the limits, incase the above functions werent able to clean up enough space
            if (hostingEnvironment.HostingEnvironmentPhysical.EntitysConsumed > hostingEnvironment.HostingEnvironmentPhysical.MaxEntitysSupported)
            {
                SimEnv.TraceLogger.LogDebug(string.Format("Restriction Checking Environment entitys consumed : {0}", hostingEnvironment.HostingEnvironmentPhysical.EntitysConsumed));
                SimEnv.TraceLogger.LogDebug(string.Format("Restriction Checking Environment max entitys supported : {0}", hostingEnvironment.HostingEnvironmentPhysical.MaxEntitysSupported));
                const string errorMsg = "Unable to free enough entitys in restriction checking, restarting sim";

                SimEnv.TraceLogger.LogWarn(errorMsg);
                SimEnv.TriggerScenarioRestart(errorMsg);
            }
        }

        private void PurgeOrganismCollection(IHostingEnvironment hostingEnvironment, SynchronizedCollection<IOrganism> organisms, List<IOrganism> tempList, string listName)
        {
            int cutCount = hostingEnvironment.HostingEnvironmentPhysical.EntitysConsumed - hostingEnvironment.HostingEnvironmentPhysical.MaxEntitysSupported;

            // if cutCount is 0 because we have such a high amount of alive organisms then remove all the dead ones
            // and purge some of the old alive ones
            while ((cutCount > 0) && (organisms.Count > 0) && (BornOrganisms(organisms)))
            {
                // todo : a possible bug could occur if only neural org left, update to check before and after counts of _aliveCutCount
                // and then break if so

                tempList.Clear();
                tempList.AddRange(organisms);

                // which element to delete
                int localDeleteNthElement = _deleteNthElement;

                // if the delete nth element and the list size are the same and the organism is not born it will get stuck in a loop
                // so set the check counter to 1 then
                if ((localDeleteNthElement == tempList.Count) || ((BornWithNeuralOrganismsExist(tempList))))
                    localDeleteNthElement = 1;

                foreach (IOrganism organism in tempList)
                {
                    // only non neural orgs, or only neural orgs left
                    if ((organism.NeuralSystem == null) || (BornWithNeuralOrganismsExist(tempList)))
                    {
                        _deleteCounter++;
                        // just delete every nth element
                        if (_deleteCounter % localDeleteNthElement == 0)
                        {
                            // make sure it is born
                            if (!organism.Born)
                                continue;

                            organism.TerminateAndDestroy("Simulation restriction trimming organism - " + listName);

                            // if we have got them all then exit
                            cutCount--;
                            if (cutCount <= 0)
                                break;
                        }
                    }

                }
            }
        }


        /// <summary>
        /// Make sure at least one of the organisms in the list has been born
        /// </summary>
        /// <param name="organisms"></param>
        /// <returns></returns>
        private static bool BornWithNeuralOrganismsExist(IEnumerable<IOrganism> organisms)
        {
            bool result = true;

            foreach (IOrganism organism in organisms)
            {
                // if one of them is born then mark yes and break
                if ((organism.Born) && (organism.NeuralSystem == null))
                {
                    result = false;
                    break;
                }
            }

            return result;
        }

        /// <summary>
        /// Make sure at least one of the organisms in the list has been born
        /// </summary>
        /// <param name="organisms"></param>
        /// <returns></returns>
        private static bool BornOrganisms(IEnumerable<IOrganism> organisms)
        {
            return organisms.Any(organism => organism.Born);
        }

        /// <summary>
        /// checking the fitness of the organism, will terminate it if not
        /// make sure its within the bounds of the environment
        /// </summary>
        /// <param name="organism">the organism to check</param>
        public void FitnessChecking(IOrganism organism)
        {
            #region Argument Validators
            if (organism == null)
                throw new ArgumentException("organism");
            #endregion

            if (organism.HostingEnvironment.BoundsChecking(organism))
            {
                SimEnv.TraceLogger.LogDebug("Fitness Checking dumped organism as it was outside bounds");
            }
        }

        /// <summary>
        /// Trim the inactive organisms to keep just the top x Number, change this method to private?
        /// </summary>
        /// <param name="inactiveOrganisms">organisms to trim</param>
        /// <param name="topCount">Keep the top x</param>
        /// <param name="fitnessFunction">fitness function used to help trim organisms</param>
        public static IList<IOrganism> TrimInactive(IEnumerable<IOrganism> inactiveOrganisms, int topCount, IFitnessFunction fitnessFunction)
        {

            #region Argument Validators
            if (!(inactiveOrganisms is IList<IOrganism>))
                throw new ArgumentException("organisms");
            if (fitnessFunction == null)
                throw new ArgumentException("fitnessFunction");
            #endregion

            // sort them all
            var tempOrganismsList = new OrganismCollection();

            // add the orgs
            // todo : refactor, there must be a better way to do this
            foreach (var organism in inactiveOrganisms)
            {
                tempOrganismsList.InactiveAdd(organism);
            }

            var fittestResults = new List<IOrganism>(fitnessFunction.Fittest(tempOrganismsList));
            var results = new List<IOrganism>();

            // trim to size we need
            if (fittestResults.Count > topCount)
            {
                results = fittestResults.GetRange(0, topCount);
            }

            return results;
        }
    }
}
